package com.phamkhanh.common;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

public class Population {

    private double doChinhXac = 0.0001;
    private int age;
    private int maxAge;
    private int numChromos;
    private int numChilds;
    private Chromo[] chromos;
    private Chromo bestChromo;
    private double pc;
    private double pm;
    private static int typeCrossOver;
    private static int typeMutation;
    private double b;
    private Random rand = new Random();
    
    public Population(Config config) {
        System.out.println("Khoi tao Population");
        // Configure cho Chromo truoc khi khoi tao Population
        config.configChromo();

        // Configure cac thong so cho Doi tuong Population
        // Khoi tao tuoi quan the la 0
        this.age = 0;
        this.b = 2;
        this.maxAge = config.getMaxAge();
        this.numChromos = config.getPopulation();
        this.pc = config.getPc();
        this.pm = config.getPm();
        this.numChilds = (int) (pc * numChromos);
        if (this.numChilds % 2 != 0) {
            this.numChilds++;
        }
        this.chromos = new Chromo[numChromos + numChilds];        
        Population.typeCrossOver = config.getTypeCrossOver();
        Population.typeMutation = config.getTypeMutation();
        
        
    }
    
    public void init() {
        System.out.println("Khoi tao quan the ban dau");
        
        //Khoi tao the he 1 ngau nhien
        for (int i = 0; i < numChromos; i++) {
            chromos[i] = new Chromo();
        }

        //Khoi tao ngau nhien ca the tot nhat
        for(int i = 0; i < numChromos; i++){
            if(bestChromo == null){
                bestChromo = chromos[i].clone();
            }else{
                if(bestChromo.value() < chromos[i].value())
                     bestChromo = chromos[i].clone();
            }
        }
        
        System.out.println(this);
        System.out.println("Ket thuc khoi tao");
    }

    public double getPc() {
        return pc;
    }

    public void setPc(double pc) {
        this.pc = pc;
    }

    public double getPm() {
        return pm;
    }

    public void setPm(double pm) {
        this.pm = pm;
    }
    
    
    public void thayDoiDoanNghiem(){
        // ta co doChinhXac, max
        float[] upper = Chromo.getUpper();
        float[] lower = Chromo.getLower();
        float[] bestGene = bestChromo.getGenes();
        float maxAbs = bestChromo.maxAbs();
        
        for(int i = 0; i < upper.length; i++){
            // Thay doi upper[i] va lower[i], tinh delta so voi bestGene[i]   lower  newlower bestgene[i]  newupper upper
            upper[i] = bestGene[i] + 10;
            lower[i] = bestGene[i] - 10;
        }
        // hien thi ket qua
        System.out.println("Cac doan tim nghiem moi :");
        for(int i =0; i< upper.length; i++){
            System.out.println("["+Chromo.getLower()[i]+","+Chromo.getUpper()[i]+"]");
        }
        
        
    }
    
    private void arthmeticCrossOver() {
        // Chon ngau nhien cha me dem di lai
        for (int i = 0; i < numChilds; i++) {
            chromos[numChromos + i] = chromos[(int) (rand.nextFloat() * (numChromos - 1))].clone();
        }

        // Cai dat phep lai so hoc sinh ra con tu cha me duoc chon
        float x1, x2, y1, y2;
        for (int i = numChromos; i < (numChromos + numChilds); i += 2) {
            // Lai chromos[i] vs chromos[i+1]
            for (int j = 0; j < Chromo.getNumGene(); j++) {
                float ran = rand.nextFloat();
                
                x1 = chromos[i].getGene(j);
                x2 = chromos[i + 1].getGene(j);
                
                y1 = ran * x1 + (1 - ran) * x2;
                y2 = (1 - ran) * x1 + ran * x2;
                
                chromos[i].setGene(j, y1);
                chromos[i + 1].setGene(j, y2);
            }
        }
    }
    
    private void simpleCrossOver() {
        System.out.println("Bat dau lai");

        // Chon ngau nhien cha me dem di lai
        for (int i = 0; i < numChilds; i++) {
            chromos[numChromos + i] = chromos[(int) (rand.nextFloat() * (numChromos - 1))].clone();
        }
        
        System.out.println("Cha me :");
        for (int i = 0; i < numChilds; i++) {
            System.out.println((numChromos + i) + "" + chromos[numChromos + i]);
        }

        // Cai dat phep lai don gian 1 diem cat sinh ra con tu cha me duoc chon
        for (int i = numChromos; i < (numChromos + numChilds); i += 2) {
            // Lai diem cat cho chromos[i] vs chromos[i+1] (so gene : numGene)
            Chromo father = chromos[i];  // Cha
            Chromo mother = chromos[i + 1];  // Me
            Chromo child1 = father.clone();  // Con 1
            Chromo child2 = mother.clone();  // Con 2

            // Chon diem lai trong khoang [0, numGene-1]
            int diemcat = (int) (rand.nextFloat() * (Chromo.getNumGene() - 1));
            System.out.println("Diem cat : " + diemcat);

            // lai father <x1,x2,..,xk,xk+1,..xn-1> vs mother <y1,y2,..,yk,yk+1,..,yn-1>
            // thanh child1 <x1,x2,..,xk,y'k,y'k+1,..,y'n-1> va child2 <y1,y2,...,x'k,x'k+1,..,x'n-1>
            float a = 1.0f;
            int p = 8;
            for (int j = 0; j < p; j++) {
                
                boolean repeated = false;
                // Cap nhat va kiem tra lai cac gene cua child1, child2 tu diemcat
                for (int k = diemcat; k < Chromo.getNumGene(); k++) {
                    child1.setGene(k, mother.getGene(k) * a + father.getGene(k) * (1 - a));
                    child2.setGene(k, mother.getGene(k) * (1 - a) + father.getGene(k) * a);

                    // Kiem tra
                    if (child1.getGene(k) < Chromo.getLower()[k] || child1.getGene(k) > Chromo.getUpper()[k]
                            || child2.getGene(k) < Chromo.getLower()[k] || child2.getGene(k) > Chromo.getUpper()[k]) {
                        repeated = true;
                        break;
                    }
                }
                // Hai child sinh ra thoa man, khong can giam a
                if (repeated == false) {
                    System.out.println("Gia tri a su dung :" + a);
                    break;
                } else {
                    a -= 1.0 / p;
                }                
            }//end for
            // Hieu chinh lai chromos[i] va chromos[i+1] (khong luu tru cha,me chuyen sang child1,child2)
            chromos[i] = child1;
            chromos[i + 1] = child2;
        }
        
        System.out.println("Cac con sinh ra : ");
        for (int i = 0; i < numChilds; i++) {
            System.out.println((numChromos + i) + "" + chromos[numChromos + i]);
        }
        System.out.println("ket thuc lai");
    }
    
    private void simpleMutation() {
        float temp;
        for (int i = 0; i < (numChromos + numChilds); i++) {
            for (int j = 0; j < Chromo.getNumGene(); j++) {                
                float ran = (float) rand.nextFloat();
                if (ran < pm) {          // doi bien gene[j] new ran < pm
                    float lower = Chromo.getLower()[j];
                    float upper = Chromo.getUpper()[j];
                    temp = (float) (lower + rand.nextFloat() * (upper - lower));
                    chromos[i].setGene(j, (float) temp);
                    System.out.println("DB ca the thu " + i + "tai x" + j + ", sau DB x" + j + ":" + temp);
                }
            }
        }
    }
    
    private void UnsyncMutation() {
        float temp;
        for (int i = 0; i < (numChromos + numChilds); i++) {
            for (int j = 0; j < Chromo.getNumGene(); j++) {                
                float ran = (float) rand.nextFloat();
                if (ran < pm) {          // doi bien gene[j] vi ran < pm
                    float lower = Chromo.getLower()[j];
                    float upper = Chromo.getUpper()[j];
                    float genej = chromos[i].getGene(j);
                    float delta;
                    // Dot bien khong dong bo, age cang lon thi khoang bien doi cang nho
                    if (rand.nextFloat() < 0.5) {
                        delta = (float) delta(age, upper - genej);  // delta thuoc [0, upper-genej]
                        temp = genej + delta;  // Gia tri dot bien genej -> upper
                        System.out.println("KDB ca the thu " + i + "tai x" + j + " --ban dau:" + genej + " --sau KDB:" + temp + " --y:" + (upper - genej) + " --delta:" + delta);
                    } else {
                        delta = (float) delta(age, genej - lower);
                        temp = genej - delta;
                        System.out.println("KDB ca the thu " + i + "tai x" + j + " --ban dau:" + genej + " --sau KDB:" + temp + " --y:" + (genej - lower) + " --delta:" + delta);
                    }
                    chromos[i].setGene(j, (float) temp);
                }
            }
        }
    }

    /**
     * Ham delta tra ve gia tri trong khoang [0,y], tuoi quan the cang lon thi
     * delta cang -> 0
     */
    private float delta(int age, float y) {
        float result;
        
        result = 1 - ((float) age) / maxAge;
        result = (float) Math.pow(result, b);
        
        float r = rand.nextFloat();
        result = (float) Math.pow(r, result);
        result = y * (1 - result);
        return result;        
    }
    
    public void crossOver() {
        switch (typeCrossOver) {
            case 1:
                arthmeticCrossOver();
                break;
            case 0:
                simpleCrossOver();
                break;
            default:
                break;
        }
    }
    
    public void mutation() {
        System.out.println("Bat dau dot bien");
        switch (typeMutation) {
            case 0:
                simpleMutation();
                break;
            case 1:
                UnsyncMutation();
                break;
            default:
                break;            
        }
        System.out.println("Ket thuc dot bien");
    }
    
    public void selection() {
        
        rouletWheelSelection();
        //signmaScaleSelection();
    }

    // Phep chon loc tu nhien bang phuong phap quay banh xe Rulet
    /**
     * Input : chromos[0] -> chromos[numChromos + numChilds]
     *
     * Ouput chromos[0] -> chromos[numChromos]
     */
    private void rouletWheelSelection() {
        System.out.println("Bat dau chon loc");
        System.out.println("truoc khi quay banh xe Rulet");
        for (int i = 0; i < numChromos + numChilds; i++) {
            System.out.println(i + "" + chromos[i] + ";");
        }
        System.out.println("Best : " + bestChromo);
        
        float F = 0.0f;                                                            // Tong do thich nghi cua quan the
        float[] fitness = new float[numChromos + numChilds];       // Mang do thich nghi cua cac ca the
        float[] p = new float[numChromos + numChilds];              // Mang sac xuat chon vi tri
        float[] q = new float[numChromos + numChilds];              // Mang sac xuat vi tri cua cac ca the trong banh xe Rullet

        // Tinh do thich nghi cua tung ca the, va tong do thich nghi cua ca quan the
        // Cap nhat lai ca the tot nhat tu dau toi gio
        for (int i = 0; i < (numChromos + numChilds); i++) {
            fitness[i] = chromos[i].value();
            F += fitness[i];
            
            if (fitness[i] > bestChromo.value()) {
                bestChromo = chromos[i].clone();
            }
        }        
        System.out.println("Tong do thich nghi :" + F);

        // Tinh sac xuat vi tri cua moi ca the trong quan the
        for (int i = 0; i < (numChromos + numChilds); i++) {
            p[i] = fitness[i] / F;
            if (i == 0) {
                q[i] = p[i];
            } else {
                q[i] = q[i - 1] + p[i];
            }
        }
        q[numChromos + numChilds - 1] = 1;
        
        System.out.println("Sac xuat chon pi :");
        for (int i = 0; i < (numChromos + numChilds); i++) {
            System.out.println("p[" + i + "]=" + p[i]);
        }
        
        System.out.println("Vi tri sac xuat qi :");
        for (int i = 0; i < (numChromos + numChilds); i++) {
            System.out.println("q[" + i + "]=" + q[i]);
        }
        
        Chromo[] temp = new Chromo[numChromos];
        // Quay banh xe Rullet numChromos lan de chon loc  numChromos trong so numChromos + numChilds
        System.out.println("Phat sinh so ngau nhien r :");
        for (int i = 0; i < numChromos; i++) {
            temp[i] = new Chromo();
            float ran = rand.nextFloat();
            System.out.println(ran);
            if (ran < q[0]) {
                temp[i] = chromos[0].clone();
            } else {
                for (int j = 1; j < (numChromos + numChilds); j++) {
                    if (ran > q[j - 1] && ran < q[j]) {
                        temp[i] = chromos[j].clone();
                    }
                }
            }
        }
        
        System.arraycopy(temp, 0, chromos, 0, numChromos);
        
        System.out.println("Ket thuc chon loc");
    }
    
    /**
     * Phép chọn lọc sử dụng signma scale
     */
    private void signmaScaleSelection() {        
        System.out.println("Bat dau chon loc");
        System.out.println("truoc khi quay banh xe Rulet");
        for (int i = 0; i < numChromos + numChilds; i++) {
            System.out.println(i + "" + chromos[i] + ";");
        }
        System.out.println("Best : " + bestChromo);

        // Tong do thich nghi cua quan the
        double[] fitness = new double[numChromos + numChilds];       // Mang do thich nghi cua cac ca the

        // Tinh do thich nghi cua tung ca the
        // Cap nhat lai ca the tot nhat tu dau toi gio
        for (int i = 0; i < (numChromos + numChilds); i++) {
            fitness[i] = chromos[i].value();
            if (fitness[i] > bestChromo.value()) {
                bestChromo = chromos[i].clone();
            }
        }

        // Sử dụng math-commons 3.0 để thực hiện chức năng thống kê
        DescriptiveStatistics stat = new DescriptiveStatistics(fitness);
        double populationMeanFitness = stat.getMean();
        double fitnessStandardDeviation = stat.getStandardDeviation();
        double F = stat.getSum();
        
        System.out.println("Tong do thich nghi :" + F);
        
        List<Chromo> temp = new ArrayList<>(numChromos);
        double ptr = Math.random();                       //Returns random number uniformly distributed in [0,1]
        double sum = 0;
        for (int i = 0; i < numChromos + numChilds; i++) {
            for (sum += getSigmaScaledFitness(fitness[i], populationMeanFitness, fitnessStandardDeviation); sum > ptr; ptr++) {
                temp.add(chromos[i]);
                System.out.println("sum: " + sum + "--ptr: " + ptr);
            }
        }
        
        
        System.arraycopy(temp.toArray(), 0, chromos, 0, numChromos);
        
        System.out.println("Ket thuc chon loc");        
    }
    
    private double getSigmaScaledFitness(double candidateFitness,
            double populationMeanFitness,
            double fitnessStandardDeviation) {
        if (fitnessStandardDeviation == 0) {
            return 1;
        } else {
            double scaledFitness = 1 + (candidateFitness - populationMeanFitness) / (2 * fitnessStandardDeviation);
            // Don't allow negative expected frequencies, use an arbitrary low but still positive
            // frequency of 1 time in 10 for extremely unfit individuals (relative to the remainder
            // of the population).
            return scaledFitness > 0 ? scaledFitness : 0.1;
        }
    }
    
    public void display() {
        System.out.println("The he  " + age);
        for (int i = 0; i < numChromos; i++) {
            System.out.println(i + "" + chromos[i] + ";");
        }
        System.out.println("Best : " + bestChromo);
    }
    
    public int getAge() {
        return this.age;
    }
    
    public void increaseAge() {
        this.age++;
    }
    
    public int getMaxAge() {        
        return maxAge;
    }
    
    public static int getTypeMutation() {
        return typeMutation;
    }
    
    public static void setTypeMutation(int typeMutation) {
        Population.typeMutation = typeMutation;
    }
    
    public Chromo getBestChromo() {
        return bestChromo;
    }
    
    @Override
    public String toString() {
        return "Population{" + "age=" + age + ", maxAge=" + maxAge + ", numChromos=" + numChromos + ", numChilds=" + numChilds + ", pc=" + pc + ", pm=" + pm + ", typeCrossOver=" + typeCrossOver + ", b=" + b + '}';
    }
}
